﻿// <copyright file="MovieReviewerDetails.cs" company="Microsoft Corporation">
// Copyright (c) 2009 Microsoft Corporation All Rights Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2009-04-08</date>
// <summary>Movie Reviewer Details service</summary>

namespace NYTimes
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Windows;
    using System.Windows.Media.Imaging;
    using System.Xml.Linq;
    using NYTimes.Core;
    using NYTimes.Data;
    using Synergist;

    /// <summary>
    /// The Critic resource type
    /// </summary>
    public enum CriticResourceType
    {
        /// <summary>
        /// Return All types of critics
        /// </summary>
        All,

        /// <summary>
        /// return Full time critics
        /// </summary>
        Fulltime,

        /// <summary>
        /// Return Part time critics
        /// </summary>
        PartTime,

        /// <summary>
        /// Returns Reviewer
        /// </summary>
        Reviewer,

        /// <summary>
        /// Specify the reviewer's name in the ReviewerName property
        /// </summary>
        ReviewerName
    }

    /// <summary>
    /// New York Times Movie Reviewer Details API
    /// </summary>
    public class MovieReviewerDetails : INotifyPropertyChanged
    {
        #region Fields

        /// <summary>
        /// 8 QPS rate limiting
        /// </summary>
        private static RateLimiting limiting = new RateLimiting("NYTMovies", 8);

        /// <summary>
        /// cache of critics by name
        /// </summary>
        private static Dictionary<string, Critic> criticCache;

        /// <summary>
        /// the web client
        /// </summary>
        private WebClient client;

        /// <summary>
        /// The single critic returned when searching by name
        /// </summary>
        private Critic critic;

        /// <summary>
        /// the reviewer's name
        /// </summary>
        private string reviewerName;

        /// <summary>
        /// the copyright text
        /// </summary>
        private string copyright;

        /// <summary>
        /// the resource type
        /// </summary>
        private CriticResourceType resourceType;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the MovieReviewerDetails class.
        /// </summary>
        public MovieReviewerDetails()
        {
            if (Application.Current.Resources.Contains(MovieReviewBase.Key))
            {
                this.ApiKey = Application.Current.Resources[MovieReviewBase.Key] as string;
            }

            this.Critics = new ObservableCollection<Critic>();

            if (criticCache == null)
            {
                criticCache = new Dictionary<string, Critic>();
            }
        }
        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// The property changed event handler
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the Movie Review API Key
        /// </summary>
        [Description("The NYT Movie Review API Key.")]
        public string ApiKey { get; set; }

        /// <summary>
        /// Gets the New York Times Copyright Message
        /// </summary>
        public string Copyright
        {
            get
            {
                return this.copyright;
            }

            private set
            {
                this.copyright = value;

                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Copyright"));
                }
            }
        }

        /// <summary>
        /// Gets the logo for Billboard
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1822", Justification = "This is exposed to a Silverlight application.")]
        public BitmapImage Logo
        {
            get
            {
                var streamResource = Application.GetResourceStream(
    new Uri("NYTimesSilverlightKit;component/Images/Logo.png", UriKind.Relative));

                var image = new BitmapImage();

#if SILVERLIGHT
                image.SetSource(streamResource.Stream);
#else
                image.StreamSource = streamResource.Stream;
#endif

                return image;
            }
        }

        /// <summary>
        /// Gets or sets the resource type to return
        /// </summary>
        [Description("The resource type to return.")]
        public CriticResourceType ResourceType
        {
            get
            {
                return this.resourceType;
            }

            set
            {
                this.resourceType = value;

                this.FetchCritics();
            }
        }

        /// <summary>
        /// Gets or sets the reviewer name to return
        /// </summary>
        [Description("The reviewer name")]
        public string ReviewerName 
        { 
            get
            {
                return this.reviewerName;
            }

            set
            {
                this.reviewerName = value;

                if (criticCache.ContainsKey(value))
                {
                    this.Critic = criticCache[value];

                    this.Critics.Clear();

                    this.Critics.Add(this.Critic);
                }
                else if (!string.IsNullOrEmpty(value))
                {
                    this.ResourceType = CriticResourceType.ReviewerName;
                }
            }
        }

        /// <summary>
        /// Gets the first critic returned
        /// </summary>
        public Critic Critic 
        { 
            get
            {
                return this.critic;
            }

            private set
            {
                this.critic = value;

                if (!criticCache.ContainsKey(this.ReviewerName))
                {
                    criticCache[this.ReviewerName] = value;
                }

                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Critic"));
                }
            }
        }

        /// <summary>
        /// Gets the critics that were returned
        /// </summary>
        public ObservableCollection<Critic> Critics { get; private set; }

        #endregion

        #region Implementation

        /// <summary>
        /// Fetch the critics
        /// </summary>
        private void FetchCritics()
        {
            this.Critics.Clear();

            if (this.client != null)
            {
                this.client.CancelAsync();

                this.client = null;
            }

            var uri = this.GenerateUri();

            if (uri == null)
            {
                return;
            }

            System.Diagnostics.Debug.WriteLine(uri);

            this.client = new WebClient();

            this.client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnCriticsReturned);

            limiting.MakeWebRequest(this.client, uri);
        }

        /// <summary>
        /// Parse the XML data returned
        /// </summary>
        /// <param name="sender">the web client</param>
        /// <param name="e">the download string completed event arguments</param>
        private void OnCriticsReturned(object sender, DownloadStringCompletedEventArgs e)
        {
            this.client = null;

            if (e.Error != null)
            {
                System.Diagnostics.Debug.WriteLine(e.Error.Message);

                return;
            }

            var doc = XDocument.Parse(e.Result);

            var resultSetCopyright = doc.Descendants("copyright").FirstOrDefault();

            if (resultSetCopyright != null)
            {
                this.Copyright = resultSetCopyright.Value;
            }

            var critics = from critic in doc.Descendants("critic")
                          select new Critic()
                          {
                              Bio = critic.Element("bio").Value,
                              DisplayName = critic.Element("display_name").Value,
                              SortName = critic.Element("sort_name").Value,
                              Image = critic.Descendants("src").FirstOrDefault() == null ? null : new BitmapImage(new Uri(critic.Descendants("src").FirstOrDefault().Value, UriKind.Absolute)),
                              ImageCredit = critic.Descendants("credit").FirstOrDefault() == null ? null : critic.Descendants("credit").FirstOrDefault().Value.Replace("<br>", "\n"),
                              SEOName = critic.Element("seo_name").Value,
                              Status = critic.Element("status").Value
                          };

            this.Critic = critics.FirstOrDefault();

            foreach (var item in critics)
            {
                this.Critics.Add(item);
            }
        }

        /// <summary>
        /// Generate the URI
        /// </summary>
        /// <returns>the REST URI</returns>
        private Uri GenerateUri()
        {
            if (string.IsNullOrEmpty(this.ApiKey))
            {
                return null;
            }

            if (this.ResourceType == CriticResourceType.ReviewerName && string.IsNullOrEmpty(this.ReviewerName))
            {
                return null;
            }

            var builder = new StringBuilder("http://api.nytimes.com/svc/movies/v2/critics/");

            switch (this.ResourceType)
            {
                case CriticResourceType.All:
                    builder.Append("all");
                    break;

                case CriticResourceType.Fulltime:
                    builder.Append("full-time");
                    break;

                case CriticResourceType.PartTime:
                    builder.Append("part-time");
                    break;

                case CriticResourceType.Reviewer:
                    builder.Append("reviewer");
                    break;

                case CriticResourceType.ReviewerName:
                    builder.Append(this.ReviewerName.Replace(' ', '.'));
                    break;
            }

            builder.AppendFormat(
                CultureInfo.InvariantCulture, 
                ".xml?api-key={0}", 
                this.ApiKey);

            return new Uri(builder.ToString(), UriKind.Absolute);
        }

        #endregion
    }
}
